# -*- coding: utf-8 -*-

"""
    http://projecteuler.net/problem=17
    
    PROBLEM
    
    If the numbers 1 to 5 are written out in words: one, two, three, four,
    five, then there are 3 + 3 + 5 + 4 + 4 = 19 letters used in total.

    If all the numbers from 1 to 1000 (one thousand) inclusive were written out
    in words, how many letters would be used?

    NOTE: Do not count spaces or hyphens. For example, 342 (three hundred and
    forty-two) contains 23 letters and 115 (one hundred and fifteen) contains
    20 letters. The use of "and" when writing out numbers is in compliance with
    British usage.
    
    
    NOTES
    
    
    REFERENCES
    
    
    PERFORMANCE
    time <function solution at 0x7f6b9768d758>: 0.006919 s

"""
#
# Import
#
import time
import math


#
# Globals / Constants
#
def timeit(f):
    def timer():
        t0 = time.time()
        returned = f()
        print "time %s: %.6f s" % (f, time.time() - t0)
        return returned
    return timer

def assert_match(value, expected):
    assert value == expected, "value %s != expected %s" % (
        value, expected)


#
# Test Case / Solution
#
@timeit    
def test_case():
    limit = 5
    expected = 19
    
    letters = word_letters(limit)
    assert_match(letters, expected)
    
    cases = [
        # number, word, count
        (1, 'one', 3),
        (10, 'ten', 3),
        (15, 'fifteen', 7),
        (37, 'thirty-seven', 11),
        (88, 'eighty-eight', 11),
        (100, 'one hundred', 10),
        (115, 'one hundred and fifteen', 20),
        (342, 'three hundred and forty-two', 23),
        (820, 'eight hundred and twenty', 21),
        (1000, 'one thousand', 11),
    ]
    
    for number, expected_word, expected_count in cases:
        word = get_word_for_number(number)
        letter_count = count_letters(word)
        assert_match(word, expected_word)
        assert_match(letter_count, expected_count)

    print "test case passed!"

@timeit
def solution():
    limit = 1000
    return word_letters(limit)
        

#
# Support Code
#
number_words = {
    1000    : 'one thousand',
    90      : 'ninety',
    80      : 'eighty',
    70      : 'seventy',
    60      : 'sixty',
    50      : 'fifty',
    40      : 'forty',
    30      : 'thirty',
    20      : 'twenty',
    19      : 'nineteen',
    18      : 'eighteen',
    17      : 'seventeen',
    16      : 'sixteen',
    15      : 'fifteen',
    14      : 'fourteen',
    13      : 'thirteen',
    12      : 'twelve',
    11      : 'eleven',
    10      : 'ten',
    9       : 'nine',
    8       : 'eight',
    7       : 'seven',
    6       : 'six',
    5       : 'five',
    4       : 'four',
    3       : 'three',
    2       : 'two',
    1       : 'one',
    0       : '',
}

def word_letters(limit):
    total_letters = 0
    
    for n in xrange(1,limit+1):
        num_letters = count_letters(get_word_for_number(n))
        total_letters += num_letters
        
    return total_letters

def get_word_for_number(n):
    if n in number_words:
        return number_words[n]
    else:
        return compute_number_word(n)
        
def compute_number_word(n):    
    # validation
    if not (n > 19 and n < 1000):
        raise ValueError("%d too large; only works for numbers 20-999")
    
    # tens
    log10 = int(math.floor(math.log10(n)))
    
    # tens
    if log10 == 1:
        ten_digit = n / 10 * 10
        one_digit = n % 10
        word = "%s-%s" % (
            number_words[ten_digit],
            number_words[one_digit])
        
    # hundreds
    elif log10 == 2:
        hundred_digit = n / 100
        the_rest = get_word_for_number((n % 100))
        word = "%s hundred and %s" % (
            number_words[hundred_digit],
            the_rest)
        
    word = word.strip()
    if word.endswith('-'):
        word = word[:-2]
    if word.endswith('and'):
        word = word[:-4]
    return word

def count_letters(word):
    word = word.replace('-', '').replace(' ', '')
    return len(list(word))
    
    

#
# Additional Tests
#



#
# Main
#
if __name__ == '__main__':
    test_case()
    print solution()
    